package com.bianmaba.spring.security.basic.configuration;


import com.bianmaba.spring.security.util.PasswordEncoderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;

import java.util.*;

import static com.bianmaba.spring.security.autoconfig.WebSecurityAutoConfiguration.ORDER;


@Configuration
//如果此类加载前已经存在指定类型的实例，则此类将不会被实例化(如果有继承关系，则可能会执行构造函数，但实际为同一个实例时为正常，否则可能不正常)
@ConditionalOnMissingBean({SecurityPropertiesConfiguration.class, SecurityPropertiesConfiguration.WebSecurityProperties.class})
@ConditionalOnWebApplication(
        type = ConditionalOnWebApplication.Type.SERVLET
)
//配置初始化排序，此值越低，越靠前，但@Import引入的类此注解无效
@Order(ORDER)
@EnableConfigurationProperties(SecurityPropertiesConfiguration.WebSecurityProperties.class)
public class SecurityPropertiesConfiguration {

    //开启配置文件支持后此类不能添加自动实例化的注解，如@Configuration,@Component等
    @ConfigurationProperties(prefix = "spring.security")
    public static class WebSecurityProperties {
        private static final Logger LOG = LoggerFactory.getLogger(WebSecurityProperties.class);
        //默认忽略的文件列表
        private String[] DEFAULT_IGNORING = new String[]{"/css/**", "/js/**", "/images/**", "/webjars/**", "/**/favicon.ico", "/**/*.css", "/**/*.js", "/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.bmp", "/**/*.icon", "/**/*.ico", "/**/*.swf"};

        public WebSecurityProperties() {
            if (this.getClass().getName().equals(SecurityPropertiesConfiguration.class.getName())) ;
            {
                LOG.info("加载安全性属性配置(order=" + ORDER + "):" + this.getClass().getName());
            }
        }

        @Autowired
        private SecurityProperties securityProperties;

        private Login login = new Login();

        private Logout logout = new Logout();
        private AccessDenied accessDenied = new AccessDenied();
        private Session session = new Session();
        /**
         * extend by bianmaba:Whether a secure connection is used
         */
        private boolean requireSsl = false;
        /**
         * extend by bianmaba:Whether to enable CSRF protection.
         */
        private boolean enableCsrf = false;
        /**
         * Configuring whether all resources need to be authenticated before accessing(true:need to authenticated)
         */
        private boolean anyRequestAuthenticated = false;
        /**
         * extend by bianmaba:User and role configuration, the same user supports multiple roles, and uses \",\" to separate.
         */
        private List<Map<String, String>> users = new ArrayList<Map<String, String>>(0);
        /**
         * extend by bianmaba:Requires authorized resource allocation, the same resource supports multiple roles, and uses \",\" to separate.
         */
        private List<Map<String, String>> resources = new ArrayList<Map<String, String>>(0);
        /**
         * extend by bianmaba:Configuring a list of permited resource.
         */
        private List<String> permited = new ArrayList<String>(0);
        /**
         * extend by bianmaba:Ignored resources (usually static).
         */
        private List<String> ignored = new ArrayList<String>(0);
        /**
         * Resources to be authenticated to access(invalid when spring.security.anyRequestAuthenticated=true)
         */
        private List<String> authenticated = new ArrayList<String>(0);
        /**
         * extend by bianmaba:Time for configuring a resource cache: milliseconds
         */
        private int resourceCacheTime = 0;
        /**
         * extend by bianmaba:Configuring users, roles, and resources is managed with a configuration file or by a database management + configuration file, configured as \"basic\" for configuration file management, and configured as \"database\" for database management + configuration files.
         */
        private String type = "basic";

        //header配置
        private Headers headers = new Headers();

        public AccessDenied getAccessDenied() {
            return accessDenied;
        }

        public void setAccessDenied(AccessDenied accessDenied) {
            this.accessDenied = accessDenied;
        }

        public Login getLogin() {
            return login;
        }

        public void setLogin(Login login) {
            this.login = login;
        }

        public Logout getLogout() {
            return logout;
        }

        public void setLogout(Logout logout) {
            this.logout = logout;
        }

        public int getResourceCacheTime() {
            return resourceCacheTime;
        }

        public void setResourceCacheTime(int resourceCacheTime) {
            this.resourceCacheTime = resourceCacheTime;
        }


        public List<Map<String, String>> getUsers() {
            return users;
        }

        public void setUsers(List<Map<String, String>> users) {
            PasswordEncoder passwordEncoder = PasswordEncoderFactory.getPasswordEncode("MD5");
            for (Map<String, String> user : users) {
                user.replace("password", "{MD5}" + passwordEncoder.encode(user.get("password")));
            }
            this.users = users;
        }

        public List<Map<String, String>> getResources() {
            return resources;
        }

        public void setResources(List<Map<String, String>> resources) {
            this.resources = resources;
        }

        public List<String> getPermited() {
            return permited;
        }

        public void setPermited(List<String> permited) {
            this.permited = permited;
        }

        public List<String> getIgnored() {
            List<String> defaults = Arrays.asList(DEFAULT_IGNORING);
            this.ignored.removeAll(defaults);
            this.ignored.addAll(defaults);
            return this.ignored;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public void setRequireSsl(boolean requireSsl) {
            this.requireSsl = requireSsl;
        }


        public void setEnableCsrf(boolean enableCsrf) {
            this.enableCsrf = enableCsrf;
        }

        public void setIgnored(List<String> ignored) {
            this.ignored = ignored;
        }


        public boolean isRequireSsl() {
            return this.requireSsl;
        }

        public boolean isEnableCsrf() {
            return this.enableCsrf;
        }

        public boolean isAnyRequestAuthenticated() {
            return anyRequestAuthenticated;
        }

        public void setAnyRequestAuthenticated(boolean anyRequestAuthenticated) {
            this.anyRequestAuthenticated = anyRequestAuthenticated;
        }

        public SecurityProperties getSecurityProperties() {
            return securityProperties;
        }

        public List<String> getAuthenticated() {
            return authenticated;
        }

        public void setAuthenticated(List<String> authenticated) {
            this.authenticated = authenticated;
        }


        public void setHeaders(Headers headers) {
            this.headers = headers;
        }

        public Headers getHeaders() {
            return headers;
        }

        public Session getSession() {
            return session;
        }

        public void setSession(Session session) {
            this.session = session;
        }
    }

    public static class Session {
        /**
         * extend by bianmaba:Configure the number of users allowed to log in (at the same time, the number of logged in). Configure 1 to indicate that only one login is allowed.
         */
        private Integer maximumSessions = 1;
        private String expiredUrl;
        private boolean maxSessionsPreventsLogin = false;
        private boolean enableSessionUrlRewriting = false;
        private String invalidSessionUrl;
        private String sessionAuthenticationErrorUrl;

        public Integer getMaximumSessions() {
            return maximumSessions;
        }

        public void setMaximumSessions(Integer maximumSessions) {
            this.maximumSessions = maximumSessions;
        }

        public String getExpiredUrl() {
            return expiredUrl;
        }

        public void setExpiredUrl(String expiredUrl) {
            this.expiredUrl = expiredUrl;
        }

        public boolean isMaxSessionsPreventsLogin() {
            return maxSessionsPreventsLogin;
        }

        public void setMaxSessionsPreventsLogin(boolean maxSessionsPreventsLogin) {
            this.maxSessionsPreventsLogin = maxSessionsPreventsLogin;
        }

        public boolean isEnableSessionUrlRewriting() {
            return enableSessionUrlRewriting;
        }

        public void setEnableSessionUrlRewriting(boolean enableSessionUrlRewriting) {
            this.enableSessionUrlRewriting = enableSessionUrlRewriting;
        }

        public String getInvalidSessionUrl() {
            return invalidSessionUrl;
        }

        public void setInvalidSessionUrl(String invalidSessionUrl) {
            this.invalidSessionUrl = invalidSessionUrl;
        }

        public String getSessionAuthenticationErrorUrl() {
            return sessionAuthenticationErrorUrl;
        }

        public void setSessionAuthenticationErrorUrl(String sessionAuthenticationErrorUrl) {
            this.sessionAuthenticationErrorUrl = sessionAuthenticationErrorUrl;
        }
    }

    public static class Login {

        private boolean permitAll = true;
        /**
         * extend by bianmaba:Login page
         */
        private String loginPage;

        private AuthenticationSuccess success = new AuthenticationSuccess();

        private AuthenticationFailure failure = new AuthenticationFailure();

        public AuthenticationSuccess getSuccess() {
            return success;
        }

        public void setSuccess(AuthenticationSuccess success) {
            this.success = success;
        }

        public AuthenticationFailure getFailure() {
            return failure;
        }

        public void setFailure(AuthenticationFailure failure) {
            this.failure = failure;
        }

        public String getLoginPage() {
            return loginPage;
        }

        public void setLoginPage(String loginPage) {
            this.loginPage = loginPage;
        }

        public boolean isPermitAll() {
            return permitAll;
        }

        public void setPermitAll(boolean permitAll) {
            this.permitAll = permitAll;
        }
    }


    public static class LogoutSuccess extends SuccessTarget {
    }

    public static class AuthenticationSuccess extends SuccessTarget {
    }

    public static class AccessDenied {
        private String errorPage;

        public String getErrorPage() {
            return errorPage;
        }

        public void setErrorPage(String errorPage) {
            this.errorPage = errorPage;
        }
    }

    public static abstract class SuccessTarget {

        private String targetUrlParameter = null;

        /**
         * extend by bianmaba:Jump page after successful login
         */
        private String defaultTargetUrl = "/";

        private boolean alwaysUseDefaultTargetUrl = false;

        private boolean useReferer = false;

        public String getTargetUrlParameter() {
            return targetUrlParameter;
        }

        public void setTargetUrlParameter(String targetUrlParameter) {
            this.targetUrlParameter = targetUrlParameter;
        }

        public String getDefaultTargetUrl() {
            return defaultTargetUrl;
        }

        public void setDefaultTargetUrl(String defaultTargetUrl) {
            this.defaultTargetUrl = defaultTargetUrl;
        }

        public boolean isAlwaysUseDefaultTargetUrl() {
            return alwaysUseDefaultTargetUrl;
        }

        public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
            this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
        }

        public boolean isUseReferer() {
            return useReferer;
        }

        public void setUseReferer(boolean useReferer) {
            this.useReferer = useReferer;
        }
    }


    public static class AuthenticationFailure {
        /**
         * extend by bianmaba:Jump page after failure login
         */
        private String defaultFailureUrl = null;
        /**
         * extend by bianmaba:forward to failure page after failure login
         */
        private boolean forwardToDestination = false;

        private boolean allowSessionCreation = true;

        public String getDefaultFailureUrl() {
            return defaultFailureUrl;
        }

        public void setDefaultFailureUrl(String defaultFailureUrl) {
            this.defaultFailureUrl = defaultFailureUrl;
        }

        public boolean isForwardToDestination() {
            return forwardToDestination;
        }

        public void setForwardToDestination(boolean forwardToDestination) {
            this.forwardToDestination = forwardToDestination;
        }

        public boolean isAllowSessionCreation() {
            return allowSessionCreation;
        }

        public void setAllowSessionCreation(boolean allowSessionCreation) {
            this.allowSessionCreation = allowSessionCreation;
        }
    }

    public static class Logout {
        /**
         * extend by bianmaba:forward to failure page after failure login
         */
        private boolean logoutUrl = false;

        private boolean permitAll = true;

        private LogoutSuccess success = new LogoutSuccess();

        public LogoutSuccess getSuccess() {
            return success;
        }

        public void setSuccess(LogoutSuccess success) {
            this.success = success;
        }

        public boolean isLogoutUrl() {
            return logoutUrl;
        }

        public void setLogoutUrl(boolean logoutUrl) {
            this.logoutUrl = logoutUrl;
        }

        public boolean isPermitAll() {
            return permitAll;
        }

        public void setPermitAll(boolean permitAll) {
            this.permitAll = permitAll;
        }
    }

    public static class Headers {

        public enum HSTS {

            NONE, DOMAIN, ALL

        }

        public enum ContentSecurityPolicyMode {

            /**
             * Use the 'Content-Security-Policy' header.
             */
            DEFAULT,

            /**
             * Use the 'Content-Security-Policy-Report-Only' header.
             */
            REPORT_ONLY

        }

        /**
         * Enable cross site scripting (XSS) protection.
         */
        private boolean xss = true;

        /**
         * Enable cache control HTTP headers.
         */
        private boolean cache = true;

        /**
         * Enable "X-Frame-Options" header.
         */
        private boolean frame = true;

        /**
         * Enable "X-Content-Type-Options" header.
         */
        private boolean contentType = true;

        /**
         * Value for content security policy header.
         */
        private String contentSecurityPolicy;

        /**
         * Content security policy mode.
         */
        private ContentSecurityPolicyMode contentSecurityPolicyMode = ContentSecurityPolicyMode.DEFAULT;

        /**
         * HTTP Strict Transport Security (HSTS) mode (none, domain, all).
         */
        private HSTS hsts = HSTS.ALL;

        public boolean isXss() {
            return this.xss;
        }

        public void setXss(boolean xss) {
            this.xss = xss;
        }

        public boolean isCache() {
            return this.cache;
        }

        public void setCache(boolean cache) {
            this.cache = cache;
        }

        public boolean isFrame() {
            return this.frame;
        }

        public void setFrame(boolean frame) {
            this.frame = frame;
        }

        public boolean isContentType() {
            return this.contentType;
        }

        public void setContentType(boolean contentType) {
            this.contentType = contentType;
        }

        public String getContentSecurityPolicy() {
            return this.contentSecurityPolicy;
        }

        public void setContentSecurityPolicy(String contentSecurityPolicy) {
            this.contentSecurityPolicy = contentSecurityPolicy;
        }

        public ContentSecurityPolicyMode getContentSecurityPolicyMode() {
            return this.contentSecurityPolicyMode;
        }

        public void setContentSecurityPolicyMode(
                ContentSecurityPolicyMode contentSecurityPolicyMode) {
            this.contentSecurityPolicyMode = contentSecurityPolicyMode;
        }

        public HSTS getHsts() {
            return this.hsts;
        }

        public void setHsts(HSTS hsts) {
            this.hsts = hsts;
        }

    }
}